/*
 Copyright (C) 2009 QDSS.org
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.qdss.persist.query;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.qdss.commons.util.log.Log;
import org.qdss.commons.util.log.LogFactory;
import org.qdss.persist.PersistManagerFactory;
import org.qdss.persist.dialect.Type;
import org.qdss.persist.exception.SqlExceptionConverter;
import org.qdss.persist.util.SqlHelper;
import org.springframework.jdbc.datasource.DataSourceUtils;

/**
 * Native sql query
 * 
 * @author <a href="mailto:zhaofang123@gmail.com">FANGFANG ZHAO</a>
 * @see 0.1, May 25, 2009
 * @version $Id: NativeQueryImpl.java 152 2012-10-22 05:14:55Z zhaofang123@gmail.com $
 */
public class NativeQueryImpl extends BaseQuery<NativeQuery> implements NativeQuery {
	private static final long serialVersionUID = -5302544598435577417L;
	
	private static final Log LOG = LogFactory.getLog(NativeQueryImpl.class);

	final private String sql;
	transient private PersistManagerFactory managerFactory;

	private Map<Integer, Object> inParameters = new HashMap<Integer, Object>();

	private Type parameterTypes[] = null;
	private Type returnTypes[] = null;

	transient private List<Object[]> dataCache = null;

	public NativeQueryImpl(String sql, PersistManagerFactory managerFactory) {
		this.sql = sql;
		this.managerFactory = managerFactory;
	}
	
	public NativeQuery setInParameterType(Type... fTypes) {
		this.parameterTypes = fTypes;
		return this;
	}

	public NativeQuery setReturnType(Type... fTypes) {
		this.returnTypes = fTypes;
		return this;
	}

	@Override
	public NativeQuery setParameter(int position, Object value) {
		this.inParameters.put(position, value);
		return this;
	}

	public Object[][] array() {
		if (execQuery(0).isEmpty())
			return AjqlResultImpl.EMPTY_OBJECT_ARRAYS;

		return dataCache.toArray(new Object[dataCache.size()][]);
	}

	public Object[] unique() {
		setMaxResults(1);
		setLimit(1, offset);
		if (execQuery(1).isEmpty())
			return null;
		
		return dataCache.get(0);
	}
	
	public NativeQuery reset() {
		if (dataCache != null)
			dataCache = null;
		return this;
	}

	/**
	 * start exec query
	 * 
	 * @param fetch
	 * @return
	 */
	protected List<Object[]> execQuery(int fetch) {
		if (dataCache != null)
			return dataCache;
		dataCache = new LinkedList<Object[]>();

		if (fetch == 1)
			setMaxResults(1);
		// else to all
		
		String aSql = sql;
		if (limit > 0) {
			aSql = managerFactory.getDialect().insertLimit(aSql, limit, offset);
		}
		if (LOG.isInfoEnabled()) {
			LOG.info(">> " + aSql);
		}

		SlowLogger.start();
		Connection connect = DataSourceUtils.getConnection(managerFactory
				.getDataSource());
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			pstmt = connect.prepareStatement(aSql);
			
			if (!inParameters.isEmpty()) {
				// set specify value
				if (parameterTypes != null && parameterTypes.length == inParameters.size()) {
					for (Map.Entry<Integer, Object> e : inParameters.entrySet()) {
						int idx = e.getKey() - 1;
						parameterTypes[idx].getFieldEditor().set( pstmt, idx + 1, e.getValue() );
					}
				} else {
					if (LOG.isDebugEnabled())
						LOG.debug("Parameter type unset, use #setObject");
					
					// set generial value
					for (Map.Entry<Integer, Object> e : inParameters.entrySet()) {
						pstmt.setObject(e.getKey(), e.getValue());
					}
				}
			}
			
			if (getTimeout() > 0)
				pstmt.setQueryTimeout(getTimeout());

			rs = pstmt.executeQuery();
			int colCount = rs.getMetaData().getColumnCount();
			
			if (fetch > 0)
				rs.setFetchSize(fetch);
			if (getFirstResult() > 0)
				rs.absolute(getFirstResult());
			
			if (getMaxResults() <= 0) {
				while (rs.next()) {
					dataCache.add( readRow(rs, colCount) );
				}
			} else {
				int current = 0;
				while (current++ < getMaxResults() && rs.next()) {
					dataCache.add( readRow(rs, colCount) );
				}
			}
			
			return dataCache;
			
		} catch (SQLException sqlex) {
			throw SqlExceptionConverter.convert(sqlex, "#NATIVE_QUERY", aSql);
		} finally {
			SqlHelper.close(rs);
			SqlHelper.close(pstmt);
			SqlHelper.release(connect, managerFactory.getDataSource());
			SlowLogger.stop(getSlowLoggerTime(), dataCache.size(), aSql);
		}
	}
	
	// read row
	Object[] readRow(ResultSet rs, int colCount) throws SQLException {
		Object[] values = new Object[colCount];
		
		if (returnTypes != null && returnTypes.length == colCount) {
			for (int i = 0; i < colCount; i++) {
				values[i] = returnTypes[i].getFieldEditor().get(rs, i + 1);
			}
		} else {
			if (LOG.isDebugEnabled())
				LOG.debug("Return type unset, use #getObject");
			
			for (int i = 0; i < colCount; i++) {
				values[i] = rs.getObject(i + 1);
			}
		}
		
		return values;
	}
}
